"""Installation command.

TODO:
- Set directory ownership/permission correctly (probably need www username).
- If settings_local.py exists, read it and use its values for defaults.
- timezone
- home directory
"""

import getpass
import grp
import ldap
import optparse
import os
import pwd
import socket
import sys

import hcp.password
import hcp.site.command

description = """Installs HCP and creates a "site" directory. The site
directory includes all configuration files and necessary files.  Installation
includes initializing LDAP and your database.
"""

class Command(hcp.site.command.Command):

    """Install command."""

    name = 'install'

    def run(self):
        self.print_intro()
        self.check_dependencies()
        self.check_site_dir()
        self.create_site_dir()
        self.ask_domain()
        self.ask_ldap()
        self.ask_database()
        self.ask_admin()

        self.write_settings_local()
        self.create_ldap_data()
        self.create_database()

        # XXX: Site root (/)
        # XXX: Webserver type
        # XXX: Python loader (modpython, fastcgi, etc.)

        self.print_outro()

    def add_options(self, parser):
        group = optparse.OptionGroup(parser, '"install" command', description)
        parser.add_option_group(group)

    def print_intro(self):
        self.site._print("""
***********************************
Hostmate Control Panel Installation
***********************************

Welcome to the Hostmate Control Panel installation utility.

This will check your system, ask you a few questions, and then perform the
installation.

""", word_wrap=False)

    def print_outro(self):
        # XXX: Instructions on what to do next.
        self.site._print("""Installation complete!""")

    def check_dependencies(self):
        # Most dependencies should be handled by setuptools,
        # but it doesn't hurt to double check.
        self._check_dependency_python()
        self._check_dependency_ldap()
        self._check_dependency_oldap()
        self._check_dependency_django()
        self._check_dependency_jinja()
        self._check_dependency_database()

    def _check_dependency_python(self):
        version = sys.version_info[:2]
        human_readable = '.'.join(map(str, version))
        if version < (2, 5):
            self.site._print('Python version %r is too old, try 2.6.' % (human_readable,))
            self.site.exit(1)
        if version == (2, 5):
            self.site._print('WARNING: This is not tested with Python 2.5.')
        if version > (2, 6):
            self.site._print("""WARNING: This appears to be a newer version of
Python than what has been tested against.  Things may not work.""")

    def _check_dependency_ldap(self):
        # XXX: Check version?
        try:
            import ldap
        except ImportError:
            self.site._print("""Python LDAP library is not installed.
See http://www.python-ldap.org/.
Please install it and try again.
""")
            self.site.exit(1)

    def _check_dependency_oldap(self):
        # XXX: Check version?
        try:
            import oldap
        except ImportError:
            self.site._print("""Python OLDAP library is not installed.
See XXX
Please install it and try again.
""")
            self.site.exit(1)

    def _check_dependency_django(self):
        # XXX: Check version?
        try:
            import django
        except ImportError:
            self.site._print("""Django is not installed.
See http://www.djangoproject.com/.
Please install it and try again.
""")
            self.site.exit(1)

    def _check_dependency_jinja(self):
        # XXX: Check version?
        try:
            import jinja2
        except ImportError:
            self.site._print("""Jinja 2 not installed.
See http://jinja.pocoo.org/2/.
Please install it and try again.
""")
            self.site.exit(1)

    def _check_dependency_database(self):
        # XXX: Currently only MySQL, but django supports many others.
        try:
            import MySQLdb
        except ImportError:
            self.site._print("""MySQL Python interface is not installed.
See http://mysql-python.sourceforge.net/
Please install it and try again.
""")
            self.site.exit(1)


    def check_site_dir(self):
        if os.path.exists(self.site.site_dir):
            result = self.site.query('The site directory %r already exists, are you sure you want to continue?' % (self.site.site_dir,),
                                     True)
            if not result:
                self.site.exit(1)

    def create_site_dir(self):
        # XXX: Permissions.
        if not os.path.exists(self.site.site_dir):
            self.site._print('The site directory %r does not exist, creating it.' % (self.site.site_dir,))
            os.makedirs(self.site.site_dir)
        for dir in ('conf', 'htdocs', 'logs'):
            dirname = os.path.join(self.site.site_dir, dir)
            if not os.path.exists(dirname):
                os.makedirs(dirname)

    def ask_domain(self):
        self.domain = self.site.prompt('Enter the domain name for your site.',
                                       'example.com')
        self.organization = self.site.prompt('Enter the name of your site.',
                                             'Example, Inc.')

    def ask_ldap(self):
        self._ask_ldap_access()
        self._ask_ldap_base()

    def _domain_dc(self):
        domain_parts = self.domain.split('.')
        domain_parts = map(lambda x: 'dc='+x, domain_parts)
        domain_dc = ','.join(domain_parts)
        return domain_dc

    def _ask_ldap_access(self):
        domain_dc = self._domain_dc()
        # Start with defaults.
        self.ldap_uri = 'ldap://127.0.0.1/'
        self.ldap_user_dn = 'cn=Manager,'+domain_dc

        while 1:
            # XXX: Validation.
            # XXX: Help on creating, permissions.
            self.ldap_uri = self.site.prompt('Enter the LDAP URI.',
                                             self.ldap_uri)

            self.ldap_user_dn = self.site.prompt("""\
Enter the LDAP user DN with full read/write privileges.
This may be the root dn.""", self.ldap_user_dn)

            self.ldap_user_pass = self.site.prompt('Enter the password for the LDAP user.',
                                                   hide_input=True)

            self.site._print('Verifying connection...')
            try:
                conn = ldap.ldapobject.SmartLDAPObject(self.ldap_uri,
                                                       who=self.ldap_user_dn,
                                                       cred=self.ldap_user_pass)
            except ldap.INVALID_CREDENTIALS:
                self.site._print('Incorrect username or password, try again.')
            except ldap.SERVER_DOWN:
                self.site._print('Server did not respond, check that the LDAP URI is correct and that the LDAP server is running.')
            except ldap.LDAPError:
                self.site._print('Unknown error.  Check that the LDAP URI is correct and try again.')
            else:
                conn.unbind_s()
                self.site._print('Connection verified.')
                break


    def _ask_ldap_base(self):
        domain_dc = self._domain_dc()
        # XXX: Help text for more detail.
        self.ldap_base_dn = self.site.prompt('Enter the base DN for your site.',
                                             domain_dc)
        # XXX: Ask for each component's base dn.
        # (Will need to think through plugin design.)

    def ask_database(self):
        #self._ask_database_engine()
        self.database_engine = 'mysql'
        if self.database_engine in ('mysql',):
            self._ask_database_name()
            self._ask_database_user()
            self._ask_database_password()
            self._ask_database_host()
            self._ask_database_port()

    def _ask_database_name(self):
        self.database_name = self.site.prompt('Enter the database name.',
                                              'hcp')

    def _ask_database_user(self):
        self.database_user = self.site.prompt('Enter the database user.',
                                              'hcp')

    def _ask_database_password(self):
        self.database_password = self.site.prompt('Enter the database password for %r.' % (self.database_user,),
                                                  hide_input=True)

    def _ask_database_host(self):
        self.database_host = self.site.prompt('Enter the database host.  Leave the input blank for localhost.',
                                              input_required=False)

    def _ask_database_port(self):
        self.database_port = self.site.prompt('Enter the database port.  Leave the input blank for the database default.',
                                              input_required=False)

    def ask_admin(self):
        # XXX: Check if admin already exists!
        default = getpass.getuser()
        if default == 'root':
            default = os.getlogin()
            if default == 'root':
                default = None

        def validator(value):
            if value == 'root':
                raise ValueError
            try:
                pwd.getpwnam(value)
            except KeyError:
                raise ValueError
            else:
                return value

        # XXX: Allow creating a new user here?
        self.admin_username = self.site.prompt("""Enter the username that will be the site admin.
This must be an existing local system account username.""",
           default,
           validator=validator,
           validator_message='The value must be a valid login on this system.  It may not be "root".')

        self.admin_password = self.site.prompt("""Enter the password for %r.""" % (self.admin_username,),
                                               hide_input=True)
        # XXX: Verify password?

        self.admin_givenName = self.site.prompt("""Enter the first name of the admin (givenName).""")
        self.admin_sn = self.site.prompt("""Enter the last name of the admin (surname).""")
        self.admin_cn = self.site.prompt("""Enter the full name of the admin (commonName).""",
                                         self.admin_givenName+' '+self.admin_sn)
        self.admin_email = self.site.prompt("""Enter the admin email address.""",
                                            self.admin_username+'@'+self.domain)

    def write_settings_local(self):
        filename = os.path.join(self.site.site_dir, 'conf', 'settings_local.py')
        if os.path.exists(filename):
            result = self.site.query("""The current config file %r already exists.
I will overwrite it with your new settings.
Are you sure you want to continue?""" % (filename,), False)
            if not result:
                self.site.exit(1)

        # XXX: Permissions, ownership.
        f = open(filename, 'w')
        f.write('DATABASE_ENGINE = \'%s\'\n' % (self.database_engine,))
        f.write('DATABASE_NAME = \'%s\'\n' % (self.database_name,))
        f.write('DATABASE_USER = \'%s\'\n' % (self.database_user,))
        f.write('DATABASE_PASSWORD = \'%s\'\n' % (self.database_password,))
        f.write('DATABASE_HOST = \'%s\'\n' % (self.database_host,))
        f.write('DATABASE_PORT = \'%s\'\n' % (self.database_port,))
        f.write('\n')
        f.write('LDAP_URI = \'%s\'\n' % (self.ldap_uri,))
        f.write('LDAP_USER_DN = \'%s\'\n' % (self.ldap_user_dn,))
        f.write('LDAP_USER_PASS = \'%s\'\n' % (self.ldap_user_pass,))
        f.write('LDAP_BASE_DN = \'%s\'\n' % (self.ldap_base_dn,))

        f.write('LDAP_BASE_DN_DOMAIN = \'ou=domains,\'+LDAP_BASE_DN\n')
        f.write('LDAP_BASE_DN_PERSON = \'ou=people,\'+LDAP_BASE_DN\n')
        f.write('LDAP_BASE_DN_GROUP = \'ou=groups,\'+LDAP_BASE_DN\n')
        f.write('LDAP_BASE_DN_HOST = \'ou=hosts,\'+LDAP_BASE_DN\n')
        f.write('LDAP_BASE_DN_MAIL_ALIAS = \'ou=mail_aliases,\'+LDAP_BASE_DN\n')

        f.close()

    def create_ldap_data(self):
        # Initialize with new settings.
        import oldap.cache
        oldap.cache.initialize(self.ldap_uri, self.ldap_user_dn, self.ldap_user_pass)

        # Create top-level organization.
        from hcp import ldap_models
        ldap_models.Organization.base_dn = self.ldap_base_dn
        ldap_models.OrganizationalUnit.base_dn = self.ldap_base_dn

        domain_parts = self.domain.split('.')

        ldap_models.Organization.objects.get_or_create(
            dc=[domain_parts[0]],
            defaults={'o':[self.organization],
                     }
        )

        # Create the organizational units.
        for ou in ('domains', 'groups', 'hosts', 'ldap', 'mail_aliases', 'people'):
            ldap_models.OrganizationalUnit.objects.get_or_create(ou=[ou])

        # Create the admin account.
        admin_data = pwd.getpwnam(self.admin_username)
        ldap_models.Person.base_dn = 'ou=people,'+self.ldap_base_dn
        person, created = ldap_models.Person.objects.get_or_create(
            uid=[self.admin_username],
            defaults={'cn':[self.admin_cn],
                      'sn':[self.admin_sn],
                      'givenName':[self.admin_givenName],
                      'uidNumber':str(admin_data.pw_uid),
                      'gidNumber':str(admin_data.pw_gid),
                      'homeDirectory':admin_data.pw_dir,
                      'loginShell':admin_data.pw_shell,
                      'userPassword':[hcp.password.hash_pw(self.admin_password)],
                      'hcpPermission':['superuser'],
                      'mail':[self.admin_email],
                     }
        )

        # Create the admin group.
        group_data = grp.getgrgid(admin_data.pw_gid)
        ldap_models.Group.base_dn = 'ou=groups,'+self.ldap_base_dn
        group, created = ldap_models.Group.objects.get_or_create(
            gidNumber=str(group_data.gr_gid),
            cn=[group_data.gr_name],
        )

        # Create the domain.
        ldap_models.Domain.base_dn = 'ou=domains,'+self.ldap_base_dn
        domain, created = ldap_models.Domain.objects.get_or_create(
            hcpDomainName=[self.domain],
            defaults={'owner':[person.dn]}
        )

        # Create the host.
        ldap_models.Host.base_dn = 'ou=hosts,'+self.ldap_base_dn
        # XXX: Should this get all IP numbers for the host?
        # XXX: Support IPv6.
        host, created = ldap_models.Host.objects.get_or_create(
            cn=[socket.getfqdn()],
            ipHostNumber=[socket.gethostbyname(socket.getfqdn())],
        )

    def create_database(self):
        self.site.run_manage_command('syncdb', ['--noinput'])
